/*
 * Console.cpp
 *
 *  Created on: Dec 25, 2009
 *      Author: artur
 */

#include <kiddie/kiddie.h>
#include <Console.h>

namespace kernel
{

  Console::Console()
  {
    video = 0;
    xpos = 0;
    ypos = 0;
    base = 'd';
  }

  Console::~Console()
  {
  }

  void
  Console::putc(char c)
  {
#if defined(ARM)
    uart_base = (unsigned long)CONFIG_UART0_BASE;
    uart_base = (unsigned long)bus_to_virt(CONFIG_UART0_BASE);
    /* Check busy flag */
    do
      {
        uart_flags = *(unsigned short *)(uart_base + 0x18);
        uart_flags &= (unsigned short)0x20;
      }
    while (uart_flags);

    *(char *)uart_base = c;
    return;
#elif defined(X86)
    int xc = xpos;
    int yc = ypos;
    if (!video)
    video = (unsigned char *)phys_to_virt(CONFIG_VIDEO_BASE);
    /* FIXME! Use virtual address 'bus_to_virt()' */
    // uart_base = (unsigned long)0x3f8;
    uart_base = (unsigned long) phys_to_virt(CONFIG_UART0_BASE);

    /* set cursor */
    *(video + (xc + yc * COLUMNS) * 2) = 0;
    *(video + (xc + yc * COLUMNS) * 2 + 1) = 0;

    if (c == '\n' || c == '\r')
      {
        newline: xpos = 0;
        ypos++;
        if (ypos > (LINES - 1))
        scroll();

        *(video + (xpos + ypos * COLUMNS) * 2) = CURSOR_CHAR;
        *(video + (xpos + ypos * COLUMNS) * 2 + 1) = CURSOR_ATTRIBUTE;

        outb_p(c, uart_base);

        return;
      }

    if (c == '\b')
      {
        if (--xpos < 0)
          {
            xpos = COLUMNS - 1;
            ypos = --ypos < 0 ? 0 : ypos;
          }

        *(video + (xpos + ypos * COLUMNS) * 2) = CURSOR_CHAR;
        *(video + (xpos + ypos * COLUMNS) * 2 + 1) = CURSOR_ATTRIBUTE;

        outb_p('\b', uart_base);
        outb_p(' ', uart_base);
        outb_p('\b', uart_base);
        return;
      }

    *(video + (xpos + ypos * COLUMNS) * 2) = c & 0xFF;
    *(video + (xpos + ypos * COLUMNS) * 2 + 1) = ATTRIBUTE;

    xpos++;

    *(video + (xpos + ypos * COLUMNS) * 2) = CURSOR_CHAR;
    *(video + (xpos + ypos * COLUMNS) * 2 + 1) = CURSOR_ATTRIBUTE;

    if (xpos >= COLUMNS)
    goto newline;

    outb_p(c, uart_base);

    return;

#endif

  }

  Console &
  Console::operator <<(char c)
  {
    putc(c);
    return *this;
  }

  Console &
  Console::operator <<(const char *s)
  {
    scroll();
    while (*s != 0)
      {
        putc(*s++);
      }
    return *this;
  }

  Console &
  Console::operator <<(int d)
  {
    char buf[20];
    itoa(buf, d);
    operator <<(buf);
    return *this;
  }

  Console &
  Console::operator <<(Console::fmt f)
  {
    base = f;
    return *this;
  }

  void
  Console::scroll(void)
  {
#if defined(X86)
    /* A blank is defined as a space... we need to give it
     *  backcolor too */
    unsigned short blank = 0x20;
    int temp = 9;

    if (!video)
    video = (unsigned char *)phys_to_virt(CONFIG_VIDEO_BASE);

    /* Row 25 is the end, this means we need to scroll up */
    if (ypos > (LINES - 1))
      {
        /* Move the current text chunk that makes up the screen
         *  back in the buffer by a line */
        temp = ypos - (LINES - 1);
        util::memcpy((void *) video, (unsigned char *) (video + temp
                * COLUMNS * 2), (LINES - temp) * COLUMNS * 2);

        /* Finally, we set the chunk of memory that occupies
         *  the last line of text to our 'blank' character */
        util::memsetw((void *) (video + (LINES - temp) * COLUMNS * 2),
            blank, COLUMNS);
        ypos = LINES - 1;
      }
#endif
  }

  void
  Console::cls(void)
  {
#if defined(X86)
    int i;
    if (!video)
    video = (unsigned char *)phys_to_virt(CONFIG_VIDEO_BASE);

    for (i = 0; i < COLUMNS * LINES * 2; i++)
      {
        *(video + i) = 0;
      }

    xpos = 0;
    ypos = 0;
#endif
  }

  /**
   *  Convert the integer D to a string and save the string in BUF.
   * If BASE is equal to 'd', interpret that D is decimal, and if BASE
   * is equal to 'x', interpret that D is hexadecimal.
   */
  void
  Console::itoa(char *buf, int d)
  {
    char *p = buf;
    char *p1, *p2;
    unsigned long ud = d;
    int divisor = 10;

    /* If %d is specified and D is minus, put `-' in the head. */
    if (base == 'd' && d < 0)
      {
        *p++ = '-';
        buf++;
        ud = -d;
      }
    else if (base == 'x')
      divisor = 16;

    /* Divide UD by DIVISOR until UD == 0. */
    do
      {
        int remainder = ud % divisor;
        *p++ = (remainder < 10) ? remainder + '0' : remainder + 'a' - 10;
      }
    while (ud /= divisor);

    /* Terminate BUF. */
    *p = 0;

    /* Reverse BUF. */
    p1 = buf;
    p2 = p - 1;
    while (p1 < p2)
      {
        char tmp = *p1;
        *p1 = *p2;
        *p2 = tmp;
        p1++;
        p2--;
      }
  }

}

